#include <mathematics/Ray.h>
#include <mathematics/Plane.h>
#include <mathematics/AABB.h>
#include <mathematics/Sphere.h>

namespace ne
{
    Ray::Ray()
        : origin(Vector3::ZERO), direction(Vector3::UNIT_Z)
    {
    }

    Ray::Ray(const Ray &r)
        : origin(r.origin), direction(r.direction)
    {
    }

    Ray::Ray(const Vector3 &pos, const Vector3 &dir)
        : origin(pos), direction(dir)
    {
        direction.normalize();
    }

    void Ray::redefine(const Vector3 &pos, const Vector3 &dir)
    {
        origin = pos;
        direction = dir;
        direction.normalize();
    }

    real Ray::normalize()
    {
        return direction.normalize();
    }

    Vector3 Ray::getPoint(const real d) const
    {
        return origin + direction * d;
    }

    Ray& Ray::operator = (const Ray &r)
    {
        origin = r.origin;
        direction = r.direction;
        return *this;
    }

    bool Ray::operator == (const Ray &r) const
    {
        return (origin == r.origin) && (direction == r.direction);
    }

    bool Ray::operator != (const Ray &r) const
    {
        return (origin != r.origin) || (direction != r.direction);
    }

    bool Ray::intersects(const AABB &a) const
    {
        if (a.isNull()) return false;
        if (a.isInfinite()) return true;

        // Ray's origin in the AABB
        if (a.intersects(origin))
        {
            return true;
        }
        // The point of AABB's center projects on ray in the AABB
        Vector3 d = a.getCenter() - origin;
        real u = d.dotProduct(direction);
        if (u < 0.0f) return false;

        return a.intersects(getPoint(u));
    }

    bool Ray::intersects(const Sphere &s) const
    {
        // Ray's origin in the sphere
        if (s.intersects(origin))
        {
            return true;
        }
        // The point of sphere's center projects on ray in the sphere
        Vector3 d = s.center - origin;
        real u = d.dotProduct(direction);
        if (u < 0.0f) return false;

        return s.intersects(getPoint(u));
    }

    bool Ray::intersects(const Plane &p) const
    {
        Vector3 point = p.projectVector(origin);
        Vector3 d = point - origin;
        real u = d.dotProduct(direction);
        // The angle between projection direction and direction
        // is lass then HALF_PI
        if (u >= 0.0f)
        {
            return true;
        }
        return false;
    }

    bool Ray::trace(const Plane &p, real &t) const
    {
        Vector3 point = p.projectVector(origin);
        Vector3 d = point - origin;
        real u = d.dotProduct(direction);
        // The angle between projection direction and direction
        // is lass then HALF_PI
        if (u >= 0.0f)
        {
            t = d.squaredLength() / u;
            return true;
        }
        return false;
    }

    std::ostream& operator << (std::ostream &o, const Ray &r)
    {
        o << "Ray(origin=" << r.origin <<
               ", direction=" << r.direction << ")";
        return o;
    }
}
